/*
 * Copyright 2011-2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0.
 * See `LICENSE` in the project root for license information.
 */

package me.ijleex.platform.core.util;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;

/**
 * IP 工具类.
 *
 * @author liym
 * @since 2021-04-04 22:34 新建
 */
public final class IPUtils {

    private IPUtils() {
        throw new IllegalAccessError("Utility class");
    }

    /**
     * 将 IPv4 地址转换成十进制数值IP
     *
     * @param ipv4 IPv4
     * @return 十进制数值IP
     */
    public static long toLong(String ipv4) {
        String[] ipParts = ipv4.split("\\.");
        long ip = 0L;
        for (String part : ipParts) {
            ip = Long.parseLong(part) | ip << 8L;
        }
        return ip;
    }

    /**
     * 将十进制数值IP转换成IPv4地址
     *
     * @param ip 十进制数值IP
     * @return IPv4
     */
    public static String toIPv4(long ip) {
        return (ip >> 24) + "."
                + ((ip & 0x00FFFFFF) >> 16) + "."
                + ((ip & 0x0000FFFF) >> 8) + "."
                + (ip & 0x000000FF);
    }

    /**
     * 获取指定网卡名的IPv4地址
     *
     * @param netCardName 网卡名称，如 eth0、ens192、……
     * @return IPv4地址
     * @author liym
     * @date 2014-04-03 11:33:00
     */
    public static String getIPv4Address(String netCardName) {
        String ip = null;
        try {
            if (isLinux()) {
                NetworkInterface netInterface = NetworkInterface.getByName(netCardName);
                Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
                boolean hasMultiIp = false;
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    if (addr instanceof Inet4Address && addr.isSiteLocalAddress() && !isReservedAddr(addr)) {
                        ip = addr.getHostAddress();
                        if (hasMultiIp) {
                            // 经测试，发现虚拟IP对应的主机名是IP地址本身
                            // 有其他虚拟ip时需要判断获取真实ip
                            String hostName = addr.getCanonicalHostName();
                            if (!hostName.equals(ip)) {
                                break;
                            }
                        }
                        hasMultiIp = true;
                    }
                }
            } else {
                InetAddress addr = InetAddress.getLocalHost();
                ip = addr.getHostAddress();
            }
        } catch (Exception e) {
            ip = "127.0.0.1";
        }
        if (ip == null) {
            ip = "127.0.0.1";
        }
        return ip;
    }

    /**
     * 获取指定网卡名的IPv6地址
     *
     * @param netCardName 网卡名称，如 eth0、ens192、……
     * @author liym
     * @date 2022-08-01 22:26:58
     */
    public static String getIPv6Address(String netCardName) {
        String ip = null;
        try {
            if (isLinux()) {
                NetworkInterface netInterface = NetworkInterface.getByName(netCardName);
                Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
                boolean hasMultiIp = false;
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    if (addr instanceof Inet6Address && !isReservedAddr(addr)) {
                        ip = addr.getHostAddress();
                        if (hasMultiIp) {
                            // 经测试，发现虚拟IP对应的主机名是IP地址本身
                            // 有其他虚拟ip时需要判断获取真实ip
                            String hostName = addr.getCanonicalHostName();
                            if (!hostName.equals(ip)) {
                                break;
                            }
                        }
                        hasMultiIp = true;
                    }
                }
            } else {
                ip = getIPv6AddressForWin();
            }
        } catch (Exception e) {
            ip = "0:0:0:0:0:0:0:1"; // ::1
        }
        if (ip == null) {
            ip = "0:0:0:0:0:0:0:1"; // ::1
        }
        return ip;
    }

    /**
     * 获取MS-Windows IPv6地址
     *
     * @return IPv6地址
     * @throws IOException 可能出现的异常
     * @since 2024-08-19 20:47
     */
    private static String getIPv6AddressForWin() throws IOException {
        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
        InetAddress inetAddr = null;
        outer:
        while (networkInterfaces.hasMoreElements()) {
            Enumeration<InetAddress> inetAddresses = networkInterfaces.nextElement().getInetAddresses();
            while (inetAddresses.hasMoreElements()) {
                inetAddr = inetAddresses.nextElement();
                // Check if it's ipv6 address and reserved address
                if (inetAddr instanceof Inet6Address && !isReservedAddr(inetAddr)) {
                    break outer;
                }
            }
        }
        if (inetAddr == null) {
            return "0:0:0:0:0:0:0:1";
        }

        String ipAddr = inetAddr.getHostAddress();
        // Filter network card No
        int index = ipAddr.indexOf('%');
        if (index > 0) {
            ipAddr = ipAddr.substring(0, index);
        }
        return ipAddr;
    }

    /**
     * 本机特殊 IP 地址
     *
     * <p>"LocalAddress" / "LinkLocalAddress" / "LoopbackAddress"</p>
     */
    private static boolean isReservedAddr(InetAddress inetAddr) {
        // isAnyLocalAddress()  - 检查 InetAddress 是否是通配符地址的实用例行程序
        // isLinkLocalAddress() - 检查 InetAddress 是否是链接本地地址的实用例行程序
        // isLoopbackAddress()  - 检查 InetAddress 是否是回送地址中的实用例行程序
        return inetAddr.isAnyLocalAddress() || inetAddr.isLinkLocalAddress() || inetAddr.isLoopbackAddress();
    }

    /**
     * 判断系统是否为 Linux
     *
     * @return true/false
     */
    private static boolean isLinux() {
        String osName = System.getProperty("os.name");
        return osName.startsWith("Linux");
    }

}
